#include <ximera/graphics/ogre/scenenode.hpp>
#include <ximera/graphics/ogre/camera.hpp>
#include <ximera/graphics/ogre/graphicssystem.hpp>
#include <ximera/graphics/ogre/world.hpp>
#include <ximera/common/utils.hpp>

namespace ximera { namespace graphics { namespace ogre
{
    SceneNode::SceneNode(World* world, GraphicsSystem* graphicsSystem, String const& name)
        : _world(world),
          _parent(0),
          _name(name)
    {
        try
        {
            _ogreSceneNode = graphicsSystem->ogreSceneManager()->getRootSceneNode()->createChildSceneNode(string_util::wstr2str(name));
        }
        catch (Ogre::Exception& e)
        {
            XIMERA_THROW_OGRE_EXCEPTION(e);
        }
    }

    SceneNode::SceneNode(World* world, SceneNode* parent, String const& name)
        : _world(world),
          _parent(parent),
          _name(name)
    {
        try
        {
            _ogreSceneNode = parent->ogreSceneNode()->createChildSceneNode(string_util::wstr2str(name));
        }
        catch (Ogre::Exception& e)
        {
            XIMERA_THROW_OGRE_EXCEPTION(e);
        }
    }

    SceneNode::SceneNode(World* world, Ogre::SceneNode* node)
        : _world(world),
          _parent(0),
          _name(string_util::str2wstr(node->getName())),
          _ogreSceneNode(node)
    {
    }

    SceneNode::~SceneNode()
    {
        removeAllChildren();

        if (_parent)
            _parent->removeChild(this);

        _ogreSceneNode->detachAllObjects();
        _world->graphicsSystem()->ogreSceneManager()->destroySceneNode(_ogreSceneNode->getName());

        _attached.clear();
    }

    IWorld* SceneNode::world()
    {
        return _world;
    }

    math::Vector3 SceneNode::position() const
    {        
        return math::Vector3(_ogreSceneNode->getPosition().ptr());
    }

    void SceneNode::position(math::Vector3 const& pos)
    {
        _ogreSceneNode->setPosition(Ogre::Vector3(pos.array()));
    }    

    math::Quaternion SceneNode::orientation() const
    {        
        return math::Quaternion(_ogreSceneNode->getOrientation().ptr());
    }

    void SceneNode::orientation(math::Quaternion const& quat)
    {
        _ogreSceneNode->setOrientation(quat.w, quat.x, quat.y, quat.z);
    }

    math::Matrix3 SceneNode::orientationMatrix() const
    {
        return math::Matrix3();
    }

    void SceneNode::orientationMatrix(math::Matrix3 const& orient)
    {
    }

    void SceneNode::scale(Real scale)
    {
        _ogreSceneNode->scale(scale, scale, scale);
    }

    void SceneNode::scale(Real x, Real y, Real z)
    {
        _ogreSceneNode->scale(x, y, z);
    }    

    void SceneNode::scale(math::Vector3 const& scale)
    {
        _ogreSceneNode->scale(scale.x, scale.y, scale.z);
    }

    void SceneNode::roll(Real angle)
    {
        _ogreSceneNode->roll(Ogre::Radian(angle));
    }

    void SceneNode::yaw(Real angle)
    {
        _ogreSceneNode->yaw(Ogre::Radian(angle));
    }

    void SceneNode::pitch(Real angle)
    {
        _ogreSceneNode->pitch(Ogre::Radian(angle));
    }

    void SceneNode::direction(math::Vector3 const& dir)
    {
        _ogreSceneNode->setDirection(Ogre::Vector3(dir.array()));
    }

    void SceneNode::lookAt(math::Vector3 const& point)
    {
        _ogreSceneNode->lookAt(Ogre::Vector3(point.array()), Ogre::Node::TS_WORLD);
    }

    String const& SceneNode::name() const
    {
        return _name;
    }

    void SceneNode::name(String const& name)
    {
        _name = name;
    }

    ISceneNode* SceneNode::parent() const
    {
        return _parent;
    }

    UInt32 SceneNode::childCount() const
    {
        return static_cast<UInt32>(_children.size());
    }    

    ISceneNode* SceneNode::child(UInt32 index) const
    {
        return _children[index];
    }

    ISceneNode* SceneNode::createChild(String const& name)
    {
        SceneNode* node = new SceneNode(_world, this, name);
        _children.push_back(node);

        return node;
    }

    void SceneNode::addChild(ISceneNode* node)
    {
        XIMERA_ASSERT(node, XIMERA_THROW(InvalidParamException, L"Parameter 'node' must not be null"));
        
        if (isChild(node))
            return;

        if (node->parent())
            node->parent()->removeChild(node);

        SceneNode* sceneNode = static_cast<SceneNode*>(node);

        _ogreSceneNode->addChild(sceneNode->ogreSceneNode());

        sceneNode->parent(this);
        _children.push_back(node);
    }

    void SceneNode::removeChild(ISceneNode* node)
    {
        XIMERA_ASSERT(node, XIMERA_THROW(InvalidParamException, L"Parameter 'node' must not be null"));
        
        // remove it from the Ogre scene node
        SceneNode* sceneNode = static_cast<SceneNode*>(node);
        _ogreSceneNode->removeChild(sceneNode->ogreSceneNode());

        // set its parent to null
        sceneNode->parent(0);

        // remove it from the children container
        _children.erase(std::remove(_children.begin(), _children.end(), node));
    }

    void SceneNode::removeAllChildren()
    {
        for (NodeContainer::iterator it = _children.begin(); it != _children.end(); ++it)
        {
            SceneNode* sceneNode = static_cast<SceneNode*>(*it);

            // set its parent to null so that it doesn't call removeChild
            sceneNode->parent(0);

            // delete the node
            delete sceneNode;
        }

        _children.clear();
    }

    bool SceneNode::isChild(ISceneNode* node)
    {
        return node->parent() == this;
    }

    ISceneNode* SceneNode::findChild(String const& name)
    {
        for (NodeContainer::iterator it = _children.begin(); it != _children.end(); ++it)
        {
            if ((*it)->name() == name)
                return *it;
        }

        return 0;
    }

    ISceneNode* SceneNode::findDescendant(String const& name)
    {
        return 0;
    }

    void SceneNode::attach(Attachable* object)
    {
        IAttachable* attachable = dynamic_cast<IAttachable*>(object);
        XIMERA_ASSERT(attachable, XIMERA_THROW(InvalidParamException, L"Object must be attachable!"));

        _ogreSceneNode->attachObject(attachable->ogreMovableObject());
        _attached.push_back(object);
    }

    void SceneNode::detach(Attachable* object)
    {
    }
}}}